﻿<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
        <title>ECMAScript Templating Benchmarks // aefxx.com</title>
        <link type="image/x-icon" href="favicon.ico" rel="Shortcut Icon">
        <link rel="stylesheet" href="external/styles.css" type="text/css"/>
        <script src="external/jquery-1.4.2.min.js" type="text/javascript"></script>
        <script src="external/jquery.benchmark.js" type="text/javascript"></script>
        <script src="external/jquery.flot.min.js" type="text/javascript"></script>
        <script src="external/jquery.mustache.js" type="text/javascript"></script>
        <script src="external/jquery.tempest.js" type="text/javascript"></script>
        <script src="external/jquery.tmpl.js" type="text/javascript"></script>
        <script src="external/underscore.js" type="text/javascript"></script>
        <script src="external/jquery.srender.js" type="text/javascript"></script>
        <script src="external/jquery.nano.js" type="text/javascript"></script>
        <script src="external/ejs_production.js" type="text/javascript"></script>
        <script src="external/pure.js" type="text/javascript"></script>
        <script src="jquery.jqote2.js" type="text/javascript"></script>
    </head>
    <body>
        <h1>ECMAScript Templating Benchmarks</h1>
        <hr/>
        <h2>&copy;2010 aefxx // powered by jQuery // idea taken from Brian Landau</h2>
        <ul id="contestants">
            <li>
                <p><input type="checkbox" name="contestant" value="srender"/> srender</p>
                <ul class="progress srender"></ul>
                <p class="number"></p>
            </li>
            <li>
                <p><input type="checkbox" name="contestant" value="mustache_js"/> mustache.js</p>
                <ul class="progress mustache_js"></ul>
                <p class="number"></p>
            </li>
            <li>
                <p><input type="checkbox" name="contestant" value="underscore"/> Underscore</p>
                <ul class="progress underscore"></ul>
                <p class="number"></p>
            </li>
            <li>
                <p><input type="checkbox" name="contestant" value="jqote2"/> jQote2</p>
                <ul class="progress jqote2"></ul>
                <p class="number"></p>
            </li>
            <li>
                <p><input type="checkbox" name="contestant" value="tempest"/> Tempest</p>
                <ul class="progress tempest"></ul>
                <p class="number"></p>
            </li>
            <li>
                <p><input type="checkbox" name="contestant" value="nano"/> nano</p>
                <ul class="progress nano"></ul>
                <p class="number"></p>
            </li>
            <li>
                <p><input type="checkbox" name="contestant" value="tmpl"/> jQuery Templating Plugin</p>
                <ul class="progress tmpl"></ul>
                <p class="number"></p>
            </li>
            <li>
                <p><input type="checkbox" name="contestant" value="ejs"/> embeddedjavascript</p>
                <ul class="progress ejs"></ul>
                <p class="number"></p>
            </li>
            <li>
                <p><input type="checkbox" name="contestant" value="pure"/> pure</p>
                <ul class="progress pure"></ul>
                <p class="number"></p>
            </li>
            <li>
                <input type="checkbox" name="check" value="0"/> Check all
            </li>
            <li>
                <button id="run">RUN</button>
                &#xa0;&#xa0;Cycles:&#xa0;
                5x <input type="radio" name="cycles" value="5" checked="checked"/>
                10x <input type="radio" name="cycles" value="10"/>
                25x <input type="radio" name="cycles" value="25"/>
                50x <input type="radio" name="cycles" value="50"/>
                &#xa0;&#xa0;Types:&#xa0;
                Simple <input type="checkbox" name="simple_test" value="1" checked="checked"/>
                Loop <input type="checkbox" name="loop_test" value="2" checked="checked"/>
            </li>
        </ul>
        <h2>█ Single Passed Run &#xa0;&#xa0; Median in ms [Arithm. AVG in ms]</h2>
        <div id="placeholder"></div>

        <div id="pure">
            <div class="test">
                <h2></h2>
                <p>The homepage is <a></a>.</p>
                <p></p>
            </div>
            <div class="comments">
                <h3></h3>
                <ul>
                    <li class="comment">
                        <h5><%= this.comments[n].name %></h5>
                        <p><%= this.comments[n].body %></p>
                    </li>
                </ul>
            </div>
        </div>

        <script type="text/x-jqote-template" id="jqote2_simple">
            <![CDATA[<div class="test"><h2>This is a test of <%= this.name %></h2><p>The homepage is <a href="<%= this.url %>"><%= this.url %></a>.</p><p>The sources is: <%= this.source %></p></div>]]>
        </script>

        <script type="text/x-jqote-template" id="jqote2_loop">
            <![CDATA[<div class="comments"><h3><%= this.header %></h3><ul><% for ( var n=0,m=this.comments.length;n<m;n++ ) { %><li class="comment"><h5><%= this.comments[n].name %></h5><p><%= this.comments[n].body %></p></li><% } %></ul></div>]]>
        </script>

        <script type="text/x-ejs" id="ejs_simple">
            <div class="test"><h2>This is a test of [%= name %]</h2><p>The homepage is <a href="[%= url %]">[%= url %]</a>.</p><p>The sources is: [%= source %]</p></div>
        </script>

        <script type="text/x-ejs" id="ejs_loop">
            <div class="comments"><h3>[%= header %]</h3><ul>[% $.each(comments, function(i, comment){ %]<li class="comment"><h5>[%= comment.name %]</h5><p>[%= comment.body %]</p></li>[% }); %]</ul></div>
        </script>

        <script type="text/x-jquery-tmpl" id="tmpl_simple">
            <![CDATA[<div class="test"><h2>This is a test of ${name}</h2><p>The homepage is <a href="${url}">${this.url}</a>.</p><p>The sources is: ${source}</p></div>]]>
        </script>

        <script type="text/x-jquery-tmpl" id="tmpl_loop">
            <![CDATA[<div class="comments"><h3>${header}</h3><ul>{{each(i, comment) comments}}<li class="comment"><h5>${comment.name}</h5><p>${comment.body}</p></li>{{/each}}</ul></div>]]>
        </script>

        <script type="text/javascript">
            var CYCLES = $('input:radio:checked').val(),
                CONVERSIONS = 1000,
                RUN_LEAP = Math.round(CONVERSIONS * 0.5),
                contestants = {};

            function shuffle(v) {
                for ( var j, x, i = v.length; i; j = parseInt(Math.random() * i), x = v[--i], v[i] = v[j], v[j] = x );
                return v;
            };

            function mean(array) {
                if ( !array.length ) return 0;

                var sum = 0;
                for ( var i=0; i < array.length; i++ )
                    sum += parseFloat(array[i], 4);
                return (1/array.length) * sum;
            }

            function median(array) {
                if ( !array.length ) return 0;

                var s = array.sort(function(a, b) {return a - b;}).length;
                return s % 2 ?
                    array[(s-1)/2] : (array[(s/2)-1] + array[s/2]) / 2;
            }

            function plot(cons) {
                var data = [], i = 1;

                for ( key in cons ) {
                    var result = {
                        median: median(cons[key].results).toPrecision(2)*1000,
                        mean: mean(cons[key].results).toPrecision(2)*1000
                    };

                    data.push({
                        label: cons[key].name,
                        data: [[i++, result.median], [i++,null]],
                        color: cons[key].color,
                        bars: {
                            show: true,
                            barWidth: 1,
                            lineWidth: 1,
                            fill: 1,
                            colors: cons[key].color
                        }
                    });

                    if ( cons[key].results.length )
                        cons[key].number.text(result.median+' ms ['+result.mean+' ms]');
                }

                $.plot($('#placeholder'), data, {
                    xaxis: { ticks: [[1.5, 'Srender'], [3.5, 'mustache.js'], [5.5, 'Underscore'], [7.5, 'jQote2'], [9.5, 'Tempest'], [11.5, 'nano'], [13.5, 'jQuery Templating Plugin'], [15.5, 'embeddedjavascript'], [17.5, 'pure']], autoscaleMargin: .02 },
                    yaxis: { min: 10, max: 150 },
                    legend: { position: 'ne' },
                    grid: {  backgroundColor: '#ffffff' }
                });
            }

            $(function() {
                var benchmarks = {
                    srender: {
                        simple: function() {$.srender(this.simple, payload.simple);},
                        loop: function() {$.srender(this.loop, payload.loop);}
                    },
                    mustache_js: {
                        simple: function() {$.mustache(this.simple, payload.simple);},
                        loop: function() {$.mustache(this.loop, payload.loop);}
                    },
                    underscore: {
                        simple: function() {this.simple(payload.simple);},
                        loop: function() {this.loop(payload.loop);}
                    },
                    jqote2: {
                        simple: function() {$.jqote(this.simple, payload.simple);},
                        loop: function() {$.jqote(this.loop, payload.loop);}
                    },
                    tempest: {
                        simple: function() {$.tempest(this.simple, payload.simple);},
                        loop: function() {$.tempest(this.loop, payload.loop);}
                    },
                    nano: {
                        simple: function() {$.nano(this.simple, payload.simple);},
                        loop: function() {
                            var nano = {comments: '', header: payload.loop.header};
                            for ( var i=0; i < payload.loop.comments.length; i++ )
                                nano.comments += $.nano(this.loop.comment, payload.loop.comments[i]);

                            $.nano(this.loop.container, nano);
                        }
                    },
                    tmpl: {
                        simple: function() {this.simple($, {data: payload.simple}).join("");},
                        loop: function() {this.loop($, {data: payload.loop}).join("");}
                    },
                    ejs: {
                        simple: function() {this.simple.render(payload.simple);},
                        loop: function() {this.loop.render(payload.loop);}
                    },
                    pure: {
                        simple: function() {this.simple(payload.simple);},
                        loop: function() {this.loop(payload.loop);}
                    }
                };

                var templates = {
                    mustache_js: {
                        simple: '<div class="test"><h2>This is a test of {{name}}</h2><p>The homepage is <a href="{{url}}">{{url}}</a>.</p><p>The sources is: {{source}}</p></div>',
                        loop: '<div class="comments"><h3>{{header}}</h3><ul>{{#comments}}<li class="comment"><h5>{{name}}</h5><p>{{body}}</p></li>{{/comments}}</ul></div>'
                    },
                    underscore: {
                        simple: _.template('<div class="test"><h2>This is a test of <%= name %></h2><p>The homepage is <a href="<%= url %>"><%= url %></a>.</p><p>The sources is: <%= source %></p></div>'),
                        loop: _.template('<div class="comments"><h3><%= header %></h3><ul><% _.each(comments, function(comment){ %><li class="comment"><h5><%= comment.name %></h5><p><%= comment.body %></p></li><% }); %></ul></div>')
                    },
                    srender: {
                        simple: '<div class="test"><h2>This is a test of <%= name %></h2><p>The homepage is <a href="<%= url %>"><%= url %></a>.</p><p>The sources is: <%= source %></p></div>',
                        loop: '<div class="comments"><h3><%= header %></h3><ul><% $.each(comments, function(i, comment){ %><li class="comment"><h5><%= comment.name %></h5><p><%= comment.body %></p></li><% }); %></ul></div>'
                    },
                    jqote2: {
                        simple: $.jqotec('#jqote2_simple'),
                        loop: $.jqotec('#jqote2_loop')
                    },
                    tempest: {
                        simple: $.tempest('simple', '<div class="test"><h2>This is a test of {{name}}</h2><p>The homepage is <a href="{{url}}">{{url}}</a>.</p><p>The sources is: {{source}}</p></div>') && 'simple',
                        loop: $.tempest('loop', '<div class="comments"><h3>{{header}}</h3><ul>{% for comment in comments %}<li class="comment"<h5>{{comment.name}}</h5><p>{{comment.body}}</p></li>{% endfor %}</ul></div>') && 'loop'
                    },
                    nano: {
                        simple: '<div class="test"><h2>This is a test of {name}</h2><p>The homepage is <a href="{url}">{url}</a>.</p><p>The sources is: {source}</p></div>',
                        loop: {
                            comment: '<li class="comment"><h5>{name}</h5><p>{body}</p></li>',
                            container: '<div class="comments"><h3>{header}</h3><ul>{comments}</ul></div>'
                        }
                    },
                    tmpl: {
                        simple: $('#tmpl_simple').template(),
                        loop: $('#tmpl_loop').template()
                    },
                    ejs: {
                        simple: new EJS({element: $('#ejs_simple')[0]}),
                        loop: new EJS({element: $('#ejs_loop')[0]})
                    },
                    pure: {
                        simple: $('#pure div.test').compile({
                            'h2': 'This is a test of #{name}',
                            'p a': 'url',
                            'p a@href': 'url',
                            'p:last-child': 'The sources is: #{source}'
                        }),
                        loop: $('#pure div.comments').compile({
                            'h3': 'header',
                            'li': {
                                'comment<-comments': {
                                    'h5': 'comment.name',
                                    'p': 'comment.body'
                                }
                            }
                        })
                    }
                };

                var payload = {
                    simple: {
                        name: 'foo',
                        url: 'http://foo.bar/foo',
                        source: 'http://foo.bar/jquery.foo.js'
                    },
                    loop: {
                        header: "My Post Comments",
                        comments: [
                            {name: "Joe", body: "Thanks for this post!"},
                            {name: "Sam", body: "Thanks for this post!"},
                            {name: "Heather", body: "Thanks for this post!"},
                            {name: "Kathy", body: "Thanks for this post!"},
                            {name: "George", body: "Thanks for this post!"}
                        ]
                    }
                };

                var color = {
                    mustache_js: '#8f04a8',
                    underscore: '#cd0074',
                    srender: '#5b4cd8',
                    jqote2: '#70e500',
                    tempest: '#0d3349',
                    nano: '#fff800',
                    tmpl: '#00f8ff',
                    ejs: '#f800ff',
                    pure: '#ff9900'
                };

                $('input[name=check]').click(function() {
                    var checked = this.checked;

                    $('input[name=contestant]').each(function() {
                        this.checked = checked;
                    });
                });

                $('input[name=contestant]').each(function(i) {
                    var key = this.value;

                    contestants[key] = {
                        name: key,
                        results: [],
                        input: $(this),
                        color: color[key],
                        number: $('p.number', $(this).parents('li')[0]),
                        progress: $('ul.progress', $(this).parents('li')[0]),
                        templates: templates[key],
                        benchmarks: benchmarks[key]
                    };
                }).click(function() {
                    $('input[name=check]')[0].checked = false;
                });

                $('#run').click(function() {
                    CYCLES = $('input:radio:checked').val();
                    $('ul.progress, p.number').empty();
                    $(this).trigger('benchmark');

                }).bind('benchmark', function() {
                    var cons = shuffle($('input[name=contestant]:checked').toArray()),
                        runs = cons.length;

                    if ( !runs ) return;

                    var test_run = setInterval(function() {
                        var contestant = null;

                        if ( !(contestant = cons.shift()) ) return;

                        if ( $('input[name=simple_test]:checked').length )
                            $.benchmark(CONVERSIONS, contestants[contestant.value], 'simple');
                        if ( $('input[name=loop_test]:checked').length )
                            $.benchmark(CONVERSIONS, contestants[contestant.value], 'loop');

                        contestants[contestant.value].progress.append('<li/>');
                    }, RUN_LEAP);

                    setTimeout(function() {
                        clearInterval(test_run);
                        ( --CYCLES ) ? $('#run').trigger('benchmark') : plot(contestants);
                    }, RUN_LEAP * runs + 1500);
                });
            });
        </script>
    </body>
</html>
